﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using MMessaging.MessagingEx.Common;
using System.Threading;

namespace MMessaging.MessagingEx.Queues
{
	/// <summary>
	/// Verbindet mehrere unidirektionale Channel-Ausgänge zu einem einzigen unidirektionalen 
	/// Channelausgang zusammen.
	/// </summary>
	/// <typeparam name="MESSAGETYPE"></typeparam>
	public class MChannelAdapterOut<MESSAGETYPE> : IMChannelEndOut<MESSAGETYPE>
		where MESSAGETYPE : class
	{
		#region private

		private object syncObj = new object();

		private IList<IMChannelEndOut<MESSAGETYPE>> _inputs;
		/// <summary>
		/// der Index auf den letzten verwendeten Channel
		/// </summary>
		private int _curInputQueueIdx = 0;
		private WaitHandle[] _waitSignals;
		/// <summary>
		/// map handleIdx (as in _waitSignals[handleIdx]) -> channelIdx (as in _inputs[channelIdx])
		/// </summary>
		private Dictionary<int, int> _mapHandleIdx2ChannelIdx;
		#endregion

		#region public

		/// <summary>
		/// Konstruktor
		/// </summary>
		/// <param name="simplexChannelEndOuts">
		/// maximal 256 channel !
		/// </param>
		public MChannelAdapterOut(
			string name,
			params IMChannelEndOut<MESSAGETYPE>[] simplexChannelEndOuts)
		{
			Name = name;
			if (simplexChannelEndOuts.Length > 256)
				throw new ApplicationException("567sdjfmwenwofd67ag");
			_inputs = simplexChannelEndOuts.ToList();
		}

		public MESSAGETYPE WaitForMessage(int timeout)
		{
			int channelIndex;
			return WaitForMessage(timeout, out channelIndex);
		}

		public MESSAGETYPE WaitForMessage(int timeout, out int channelIndex)
		{
			channelIndex = 0;

			IList<IMChannelEndOut<MESSAGETYPE>> copyInputs;
			WaitHandle[] copyWaitSignals;

			lock (syncObj)
			{
				copyInputs = _inputs.ToList();
				copyWaitSignals = WaitSignals;
			}

			if (copyInputs.Count() == 0)
				return null;

			_curInputQueueIdx++;

			if (_curInputQueueIdx > copyInputs.Count)
				_curInputQueueIdx = 0;

			// prüft ob irgendein Channel Messages bereitstehen hat.
			// Beginnt mit dem Nachfolger des zuletzt verwendeten Channels.
			for (int i = _curInputQueueIdx; i < _curInputQueueIdx + copyInputs.Count; i++)
			{
				var curInput = copyInputs[i % _inputs.Count];

				var msg = curInput.WaitForMessage(0);

				if (msg != null)
				{
					_curInputQueueIdx = i % copyInputs.Count;
					channelIndex = _curInputQueueIdx;
					return msg;
				}
			}

			// there is no message, so wait
			int signalingWaitHandleIdx = WaitHandle.WaitAny(copyWaitSignals, timeout);
			if (signalingWaitHandleIdx == WaitHandle.WaitTimeout)
				return null;

			// aus index des signalisierenden Waithandles den Index des Channels ermitteln.
			_curInputQueueIdx = _mapHandleIdx2ChannelIdx[signalingWaitHandleIdx];
			channelIndex = _curInputQueueIdx;
			return copyInputs[channelIndex].WaitForMessage(0);
		}

		/// <summary>
		/// Fügt Ausgang eines unidirektionalen Channels zu dieser Ausgangszusammenfassung
		/// hinzu.
		/// </summary>
		/// <param name="simplexChannelEndOut"></param>
		public void AddChannel(IMChannelEndOut<MESSAGETYPE> simplexChannelEndOut)
		{
			lock (syncObj)
			{
				_inputs.Add(simplexChannelEndOut);
			}
		}

		public string Name { get; private set; }

		/// <summary>
		/// Array aller Wait-Handle aller untergeordneten Channels.
		/// </summary>
		public WaitHandle[] WaitSignals
		{
			get 
			{
				if (_waitSignals == null)
				{
					var waitSignals = new List<WaitHandle>();
					_mapHandleIdx2ChannelIdx = new Dictionary<int, int>();
					int handleIdx = 0;
					int countChannels = _inputs.Count;
					for(int channelIdx = 0; channelIdx< countChannels; channelIdx++)
					{
						var waitHandles = _inputs[channelIdx].WaitSignals;
						waitSignals.AddRange(waitHandles);
						var countWaitHandles = waitHandles.Length;

						for(int i=0; i< countWaitHandles;i++)
						{
							_mapHandleIdx2ChannelIdx[handleIdx] = channelIdx;
							handleIdx++;
						}
					}

					_waitSignals = waitSignals.ToArray();
				}

				return _waitSignals;
			}
		}

		#endregion
	}
}
